package com.hwang.mvc.servlet;

import com.hwang.mvc.annotations.*;
import com.hwang.mvc.pojo.Handler;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author hwangrq
 * @date 2021/2/19 16:12
 * @description DispatcherServlet
 */
public class MyDispatcherServlet extends HttpServlet {

    /** 配置文件信息 */
    private Properties properties = new Properties();

    /** 扫描缓存到的全限定类名 */
    private List<String> classNames = new ArrayList<>();

    /** ioc容器 */
    private Map<String, Object> ioc = new HashMap<>();

    /** 存储url和Method之间的映射关系  */
    private List<Handler> handlerMapping = new ArrayList<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1. 加载配置文件 spring-mvc.properties
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        // 2. 扫描相关的类，扫描注解
        doScan(properties.getProperty("scanPackage"));

        // 3. 初始化bean对象（实现ioc容器基于注解）
        doInstance();

        // 4. 实现依赖注入
        doAutowired();

        // 5. 构造一个HandlerMapping处理器映射器，将配置好的url和method建立映射关系
        initHandlerMapping();

        System.out.println("MVC 初始化完成...");
    }

    /**
     * 构造一个HandlerMapping处理器映射器，
     * 将配置好的url和method建立映射关系
     */
    private void initHandlerMapping() {
        if (ioc.isEmpty()) {return;}
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();
            String baseUrl = "";
            if (aClass.isAnnotationPresent(MyController.class)) {

                if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
                    baseUrl = aClass.getAnnotation(MyRequestMapping.class).value();
                }

                Method[] methods = aClass.getMethods();
                for (Method method : methods) {

                    if (method.isAnnotationPresent(MyRequestMapping.class)) {
                        String methodUrl = method.getAnnotation(MyRequestMapping.class).value();
                        String url = "/" + baseUrl.replaceAll("/", "") +
                                "/" + methodUrl.replaceAll("/", "");

                        // 把method所有信息及url封装为一个Handler
                        Handler handler = new Handler();
                        handler.setController(entry.getValue());
                        handler.setMethod(method);
                        handler.setPattern(Pattern.compile(url));

                        Parameter[] parameters = method.getParameters();
                        for (int i = 0; i < parameters.length; i++) {
                            Parameter parameter = parameters[i];
                            if (parameter.getType() == HttpServletRequest.class
                                    || parameter.getType() == HttpServletResponse.class) {
                                handler.getParamIndexMapping().put(parameter.getType().getSimpleName(), i);
                            } else {
                                handler.getParamIndexMapping().put(parameter.getName(), i);
                            }
                        }

                        if (method.isAnnotationPresent(MySecurity.class)) {
                            String[] values = method.getAnnotation(MySecurity.class).value();
                            List<String> methodValues = Arrays.asList(values);
                            handler.getSecurity().addAll(methodValues);

                            if (aClass.isAnnotationPresent(MySecurity.class)) {
                                String[] classValues = aClass.getAnnotation(MySecurity.class).value();
                                for (String classValue : classValues) {
                                    if (!methodValues.contains(classValue)) {
                                        handler.getSecurity().add(classValue);
                                    }
                                }
                            }
                        }
                        handlerMapping.add(handler);
                    }

                }
            }
        }

    }

    /**
     * 实现依赖注入
     */
    private void doAutowired() {
        if (ioc.isEmpty()) {return;}

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (declaredField.isAnnotationPresent(MyAutowired.class)) {
                    String beanName = declaredField.getAnnotation(MyAutowired.class).value();
                    if (StringUtils.isBlank(beanName)) {
                        // 根据字段类型注入 -- 接口类型的全限定类名
                        beanName = declaredField.getType().getName();
                    }
                    // 赋值
                    declaredField.setAccessible(true);
                    try {
                        declaredField.set(entry.getValue(), ioc.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

    /**
     * ioc容器
     * 基于classNames缓存的类的全限定类名，以及反射技术，完成对象创建和管理
     */
    private void doInstance() {
        if (classNames.size() == 0) {return;}

        try {
            for (String className : classNames) {
                Class<?> aClass = Class.forName(className);

                if (aClass.isAnnotationPresent(MyController.class)) {
                    String beanName = aClass.getAnnotation(MyController.class).value();
                    if (StringUtils.isBlank(beanName)) {
                        beanName = lowerFirst(aClass.getSimpleName());
                    }
                    ioc.put(beanName, aClass.newInstance());
                } else if (aClass.isAnnotationPresent(MyService.class)) {
                    String beanName = aClass.getAnnotation(MyService.class).value();
                    if (StringUtils.isBlank(beanName)) {
                        beanName = lowerFirst(aClass.getSimpleName());
                    }
                    ioc.put(beanName, aClass.newInstance());

                    // service层往往是有接口的，面向接口开发，此时再以接口名为id，放入一份对象到ioc中，便于后期根据接口类型注入
                    Class<?>[] interfaces = aClass.getInterfaces();
                    for (Class<?> anInterface : interfaces) {
                        // 以接口的全限定类名作为id放入
                        ioc.put(anInterface.getName(), aClass.newInstance());
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String lowerFirst(String name) {
        char[] chars = name.toCharArray();
        if ('A' <= chars[0] && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return String.valueOf(chars);
    }


    /**
     * 扫描类
     * @param scanPackage com.hwang.demo
     *                    package---->  磁盘上的文件夹（File）  com/hwang/demo
     */
    private void doScan(String scanPackage) {
        String scanPackagePath = Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource("")).getPath()
                + scanPackage.replaceAll("\\.", "/");
        File packageFile = new File(scanPackagePath);

        File[] files = packageFile.listFiles();
        assert files != null;
        for (File file : files) {
            if (file.isDirectory()) {
                doScan(scanPackage + "." + file.getName());
            } else {
                String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                classNames.add(className);
            }
        }

    }

    private void doLoadConfig(String contextConfigLocation) {
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            assert inputStream != null;
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    /**
     * 处理请求：根据url，找到对应的Method方法，进行调用
     * @param req HttpServletRequest
     * @param resp HttpServletResponse
     * @throws ServletException ServletException
     * @throws  IOException IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Handler handler = getHandler(req);
        if (handler == null) {
            resp.getWriter().print("404 not found");
            return;
        }

        Object[] args = new Object[handler.getMethod().getParameterCount()];
        Map<String, String[]> parameterMap = req.getParameterMap();
        List<String> authParams = new ArrayList<>();

        for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
            String value = StringUtils.join(param.getValue(), ",");
            value = new String(value.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
            authParams.add(value);
            if (handler.getParamIndexMapping().containsKey(param.getKey())) {
                Integer index = handler.getParamIndexMapping().get(param.getKey());
                args[index] = value;
            }
        }

        Integer reqIndex = handler.getParamIndexMapping().get(HttpServletRequest.class.getSimpleName());
        if (reqIndex != null) {
            args[reqIndex] = req;
        }

        Integer respIndex = handler.getParamIndexMapping().get(HttpServletResponse.class.getSimpleName());
        if (respIndex != null) {
            args[respIndex] = req;
        }

        boolean flag = false;
        Set<String> security = handler.getSecurity();
        if (!authParams.isEmpty()) {
            if (security.contains(authParams.get(0))) {
                flag = true;
            }
        }

        // 最终调用handler的method属性
        try {
            if (flag) {
                Object invoke = handler.getMethod().invoke(handler.getController(), args);
                resp.setContentType("text/html;charset=UTF-8");
                resp.getWriter().print(invoke);
            } else {
                resp.getWriter().write("403 no permission");
            }

        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据uri获取到能够处理当前请求的hanlder
     * 从handlerMapping中（list）
     * @param req HttpServletRequest
     * @return Handler
     */
    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) {return null;}

        String uri = req.getRequestURI();

        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.getPattern().matcher(uri);
            if (matcher.matches()) {
                return handler;
            }
        }
        return null;
    }


}
